<?php
/**
 *  💡 免责声明：本软件不得用于商业用途，仅做学习交流
 *  ⚠️ 权利声明：本软件由版权人提供产品技术支持
 *  📅 项目编号：NX2025-0135
 */
namespace App\Jobs;

use App\Models\PerformanceRecord;
use App\Models\PointAccount;
use App\Models\User;
use App\Service\Pools\DividendService;
use App\Service\User\PointAccountServices;
use App\Service\User\RewardAccountService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

/**
 *业绩奖励 普通商品购买 拼团成功奖励. 当前开启无限级别用于测试.
 */
class PerformanceRewardJob implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public $tries = 5;

    public $maxExceptions = 3;

    public $backoff = [30, 60, 300, 600, 1800];

    public $timeout = 300;

    public $user;

    public $amount;

    public $type;

    /**
     * 解除任务唯一锁的秒数.
     *
     * @var int
     */
    public $uniqueFor = 3600;

    public function __construct(User $user, $amount, $type)
    {
        $this->user = $user;
        $this->amount = $amount;
        $this->type = $type == 1 ? '新零售' : '拼团成功';
    }

    /**
     * 任务的 unique ID (唯一ID).
     *
     * @return string
     */
    public function uniqueId()
    {
        return $this->user->id;
    }

    public function handle()
    {
        $userData = $this->obtainQualifiedUsers();

        if ($userData->isNotEmpty()) {
            $this->departmentalRewards($userData);
        }
    }

    /**
     * 部门奖励
     * 根据部门等级 新增每单奖励 百分比.
     * @param mixed $userData
     */
    private function departmentalRewards($userData)
    {
        //TODO取出最大配置
        $data = $userData->map(function ($user) {
            if ($this->type == '新零售') {
                return [
                    'user_id' => $user->id,
                    'distance' => $user->distance,
                    'agent_level' => $user->agent_level,
                    'new_order_rate' => $user->performanceLevel->new_order_rate,
                    'peer_rates_one' => $user->performanceLevel->peer_rates[1],
                    'peer_rates_two' => $user->performanceLevel->peer_rates[2],
                ];
            }

            return [
                'user_id' => $user->id,
                'distance' => $user->distance,
                'agent_level' => $user->agent_level,
                'new_order_rate' => $user->performanceLevel->new_group_rate,
                'peer_rates_one' => $user->performanceLevel->peer_rates[1],
                'peer_rates_two' => $user->performanceLevel->peer_rates[2],
            ];

        });
        //计算差级 返回 新的数据
        $data = $this->calcDifference($data);
        //移除最近奖励第一个人
        $firstUser = $data->shift();

        //平级
        $equalLevel = $data
            ->sortBy('distance')
            ->where('agent_level', $firstUser['agent_level'])
            ->where('distance', '<=', $firstUser['distance'] + 2)  //顺序平级 如果跳级就不要
;

        //第一个分
        $firstUserAmount = bcmul($this->amount, $firstUser['diff'], 2);
        $this->divideTheMoney(
            $firstUser,
            $firstUserAmount,
            sprintf('%s,业绩奖励:%.2f', $this->type, $firstUserAmount)
        );

        $data->each(function ($user) {
            if ($user['diff'] != 0) {
                //后续要分的按差级分
                $amount = bcmul($this->amount, $user['diff'], 2);
                $this->divideTheMoney(
                    $user,
                    $amount,
                    sprintf('%s,业绩奖励:%.2f', $this->type, $amount)
                );
            }
        });

        if ($equalLevel->isNotEmpty()) {
            //平级奖励
            $this->equalLevelReward($equalLevel, $firstUser, $firstUserAmount);
        }
    }

    /**
     * 最终给钱逻辑.
     * @param $user
     * @param $money
     * @param $description
     * @param mixed $type
     */
    private function divideTheMoney($user, $money, $description, $type = 1)
    {
        if ($user && $money > 0) {
            //给钱
            $pointAccount = PointAccount::query()->where('user_id', $user['user_id'])->first();
            $withdraw_point = bcsub($pointAccount->available_points, $pointAccount->frozen_points, 6);
            if ($withdraw_point < $money) {
                $oringinalMoney = $money;
                $money = $pointAccount->available_points;
                DividendService::make()->injectFunds(bcsub($oringinalMoney, $money, 2));
                $description = sprintf('%s 积分不足燃烧 实际燃烧:%s', $description, $money);
            }
            PointAccountServices::make()->consumption($user['user_id'], $money, $description, 4);
            RewardAccountService::make()->recharge($user['user_id'], $money, 2, $description);
            //写日志
            PerformanceRecord::query()->create([
                'user_id' => $user['user_id'],
                'level_id' => $user['agent_level'],
                'amount' => $money,
                'description' => $description,
                'type' => $type,
            ]);
        }
    }

    /**
     * 计算差集边界保护.
     *
     * @param mixed $datas
     */
    private function calcDifference($datas)
    {
        //最大的每单率 0.2
        $maxRate = $datas->max('new_order_rate');

        //diff 滑动差值
        $diff = $this->slidingDifference($datas->pluck('new_order_rate')->toArray(), $maxRate);
        if ($diff->isEmpty()) {
            //就一个人 分全部
            $diff->push($maxRate);
        }
        $countDiff = 0;

        $datas = $datas->map(function ($item) use ($maxRate, &$diff, &$countDiff) {
            $nowDiff = $diff->shift() ?? 0;
            if (bcadd($countDiff, $nowDiff, 2) >= $maxRate && $nowDiff != 0) {
                //如果不需要最大边界保护就改成$nowDiff
                $item['diff'] = bcsub($maxRate, $countDiff, 2);
                $nowDiff = null;
            } else {
                $item['diff'] = $nowDiff;
            }
            $countDiff = bcadd($countDiff, $item['diff'], 2);
            return $item;
        });
        return collect($datas->toArray());
    }

    /**
     * 计算滑动差值
     * @param array $data 输入数据
     * @param mixed $maxRate
     * @return array|\Illuminate\Support\Collection
     */
    private function slidingDifference(array $data, $maxRate)
    {
        $fistIsMax = false;
        $first = true;
        $diffs = collect();
        $lastData = 0;
        for ($i = 0; $i < count($data) - 1; ++$i) {
            //第一个最大就 后面全是0 第一个不变保存 后面差集
            if ($first) {
                //第一个
                if ($data[$i] == $maxRate) {
                    //如果第一个就是最大的 那后面不用算了
                    $fistIsMax = true;
                }
                $diffs->push($data[$i]);
                $first = false;
                $lastData = $data[$i];
            }
            if ($fistIsMax) {
                $diffs->push(0);
            } else {
                $value = floatval(bcsub($data[$i + 1], $lastData, 2));
                if ($value > 0) {
                    $lastData = $data[$i + 1];
                }
                $diffs[] = $value;
            }
        }
        return $diffs;
    }

    /**
     * 平级奖励 上级 上上级奖励.
     * @param mixed $userData
     * @param mixed $firstUser
     * @param mixed $firstUserAmount
     */
    private function equalLevelReward($userData, $firstUser, $firstUserAmount)
    {
        $peer_rates_one = $firstUser['peer_rates_one'];
        $peer_rates_two = $firstUser['peer_rates_two'];
        $peer_rates_one_amount = bcmul($firstUserAmount, $peer_rates_one, 2);
        $peer_rates_two_amount = bcmul($peer_rates_one_amount, $peer_rates_two, 2);
        $user_one = $userData->shift();
        $user_two = $userData->shift();
        $this->divideTheMoney(
            $user_one,
            $peer_rates_one_amount,
            sprintf('平一级奖励:%.2f', $peer_rates_one_amount),
            2
        );
        $this->divideTheMoney(
            $user_two,
            $peer_rates_two_amount,
            sprintf('平二级奖励:%.2f', $peer_rates_two_amount),
            2
        );
    }

    /**
     * 获取合格用户 按照深度排序.
     * @return mixed
     */
    private function obtainQualifiedUsers()
    {
        return $this->user->queryAncestors()
            ->with('performanceLevel')
            ->select(['id', 'agent_level', 'distance'])
            ->where('agent_level', '<>', 0)
            ->where('is_dividend', 1)
            ->get()
            ->sortBy('distance');
    }
}
